#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/select.h>
#include "inet.hpp"
#include "easylog.hpp"

namespace server 
{

const int GPORT = 8080;
const int GSIZE = 10;

enum event_type
{
    read_event   = 0x1 << 1,
    write_event  = 0x1 << 2,
    except_event = 0x1 << 3,
};

struct fd_collection
{
    fd_collection() {}

    fd_collection(const fd_collection& fds)
    {
        _rfds = fds._rfds;
        _wfds = fds._wfds;
        _efds = fds._efds;
        _maxfd = fds._maxfd;
    }

    bool set(int event, int fd)
    {
        if (_fdarr.size() >= GSIZE)
            return false;

        if (event & read_event)   _rfds.set(fd);
        if (event & write_event)  _wfds.set(fd);
        if (event & except_event) _wfds.set(fd);

        _fdarr.push_back(fd);
        if (_maxfd < fd) _maxfd = fd;
        return true;
    }

    void clear(int fd)
    {
        _rfds.clear(fd);
        _wfds.clear(fd);
        _efds.clear(fd);

        for (int i = 0; i < _fdarr.size(); i++)
        {
            if (_fdarr[i] == fd) _fdarr[i] = -1;
        }
    }

    class file_descptrs
    {
    public:
        file_descptrs() { bzero(); }
        ~file_descptrs() {}

        void set  (int fd) { FD_SET(fd, &_set);          }
        void clear(int fd) { FD_CLR(fd, &_set);          }
        bool isset(int fd) { return FD_ISSET(fd, &_set); }
        void bzero()       { FD_ZERO(&_set);             }

        fd_set* get() { return &_set; }

    private:
        fd_set _set;
    };

    file_descptrs _rfds;
    file_descptrs _wfds;
    file_descptrs _efds;
    
    std::vector<int> _fdarr;
    int _maxfd = -1;
};


class select_server : public inet::tcp::server
{
public:
    select_server(uint16_t port) : server(port), _wouldblock(true)
    {}
    select_server(uint16_t port, int sec, int usec) : server(port), _timeout({sec, usec})
    {}

    void start()
    {
        _fds.set(read_event, _sock);

        while (true)
        {
            int n = 0;
            struct timeval timeout = _timeout;
            fd_collection fds_cp(_fds);

            if (_wouldblock) 
                n = select(fds_cp._maxfd+1, fds_cp._rfds.get(), fds_cp._wfds.get(), fds_cp._efds.get(),  nullptr);
            else             
                n = select(fds_cp._maxfd+1, fds_cp._rfds.get(), fds_cp._wfds.get(), fds_cp._efds.get(), &timeout);
            
            switch (n)
            {
            case 0:
                INFO("time out: %.2f", timeout.tv_sec + timeout.tv_usec / 1.0 / 1000);
                break;
            case -1:
                ERROR("select error, %d %s", errno, strerror(errno));
                break;
            default:
                handler_event(fds_cp);
                break;
            }
        }
    }

private:
    void handler_event(fd_collection& resfds)
    {
        for (auto fd : _fds._fdarr)
        {
            if (fd == -1)
                continue;

            if (resfds._rfds.isset(fd))
            {
                if (fd == _sock) 
                {
                    acceptor();
                }
                else 
                {
                    std::string buf;
                    recver(fd, &buf);
                }
            }
            if (resfds._wfds.isset(fd))
            {
                std::string msg = "test";
                sender(fd, msg);
            }
            if (resfds._efds.isset(fd))
            {
                WARN("excepton event occurred, fd: %d", fd);
            }
        }
    }

    void acceptor()
    {
        std::string cip;
        uint16_t cport;
        int sock = accept(&cip, &cport);

        INFO("a connect %d has been accepted [%s:%d]", sock, cip.c_str(), cport);

        // if (!_fds.set(read_event | write_event | except_event, sock))
        if (!_fds.set(read_event, sock))
        {
            close(sock);
            WARN("connect close, fd array is full");
        }
    }

    void recver(int fd, std::string* buf)
    {
        ssize_t s = recv(fd, buf, 1024);
        if (s > 0)
        {
            std::cout << *buf << std::endl;
        }
        else
        {
            if (s == 0) INFO("client quit");
            else WARN("recv error, %d %s", errno, strerror(errno));

            _fds.clear(fd);
            close(fd);
        }
    }

    void sender(int fd, const std::string& msg)
    {
        size_t s = send(fd, msg);

        if (s <= 0)
        {
            if (s == 0) INFO("client quit");
            else WARN("send error, %d %s", errno, strerror(errno));

            _fds.clear(fd);
            close(fd);
        }
    }


private:
    bool _wouldblock;
    struct timeval _timeout;

    fd_collection _fds;
};

}
